package whut.GA;

import org.apache.commons.math3.genetics.*;
import org.apache.commons.math3.util.FastMath;
import whut.commons.BaseBlock;
import whut.commons.IProblemSet;
import whut.commons.Location;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

/**
 * Created by Justin on 2018-04-20
 */
public class GAProcess extends BaseBlock implements IGAConstants{
    protected GeneticAlgorithm gam;

    protected Random generator;
    protected IProblemSet problem;
    protected IBinaryEncoder encoder;

    public GAProcess(IProblemSet problem, IBinaryEncoder binaryEncoder) {
        this.problem = problem;
        this.encoder = binaryEncoder;
        BinaryArray.setProblem(problem);
        BinaryArray.setEncoder(binaryEncoder);

        generator = new Random();

        gam = new GeneticAlgorithm(new OnePointCrossover<Integer>(),
//        gam = new GeneticAlgorithm(new NPointCrossover<Integer>(2),
                CROSSOVER_RATE, // all selected chromosomes will be recombined (=crosssover)
                new BinaryMutation(),
                MUTATION_RATE,
                new TournamentSelection(TOURNAMENT_ARITY)){
            @Override
            public Population evolve(Population initial, StoppingCondition condition) {
//                Population current = super.evolve(initial, condition);
                Population current = initial;

                int generationsEvolved = 0;
                int count = 0;
                while (!condition.isSatisfied(current)) {
                    current = nextGeneration(current);
                    generationsEvolved++;

                    Chromosome bestCurrent = current.getFittestChromosome();
                    mLog.trace("--> {} : {}",generationsEvolved, bestCurrent);
                   if (FastMath.abs(bestCurrent.getFitness() - 0) < problem.getERR_TOLERANCE()) break;
                }
                mLog.info("--> End of iteration : {}",generationsEvolved);
                return current;
            }

            @Override
            public Population nextGeneration(Population current) {
                return super.nextGeneration(current);
            }
        };
    }

    /**
     * Initializes a random population.
     */
    private ElitisticListPopulation randomPopulation() {
        List<Chromosome> popList = new LinkedList<>();

        for (int i=0; i<POPULATION_SIZE; i++) {
            int DIM = problem.getPROBLEM_DIMENSION();
            double[] loc = new double[DIM];
            for (int j = 0; j < DIM; j++) {
                loc[j] = problem.getLOC_LOW() + generator.nextDouble() * (problem.getLOC_HIGH() - problem.getLOC_LOW());
            }
            Location location = new Location(loc);
            AbstractListChromosome randChrom = BinaryArray.createBinaryChromosome(location);
            popList.add(randChrom);
        }
        return new ElitisticListPopulation(popList, popList.size(), ELITISM_RATE);
    }

    public Location execute(){
        // initial population
        Population initial = randomPopulation();
        // stopping conditions
        StoppingCondition stopCond = new FixedGenerationCount(NUM_GENERATIONS);

        // best initial chromosome
        Chromosome bestInitial = initial.getFittestChromosome();

        // run the algorithm
        Population finalPopulation = gam.evolve(initial, stopCond);

        // best chromosome from the final population
        Chromosome bestFinal = finalPopulation.getFittestChromosome();
        mLog.info("Best Result: {}\n", bestFinal);

        return null;
    }
}
